/**
 * Written By: MinHo Kim (minho.kim.korea@gmail.com)
 * Copyright (C) 2011, MinHo Kim, Software Expert Program, KAIST University, Seoul, Korea.
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License as published 
 * by the Free Software Foundation; version 2 of the License.
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU General Public License for more details.
 */
package kaist.sep502.tsp.model;

import java.util.List;

import kaist.sep502.tsp.util.TspUtil;

/**
 * @author minho kim
 *
 * 성능 향상을 위해서 public 멤버로 처리한다.
 */
public class Problem {
	public List<City> cityList;
//	public Distance[][] distances;
	public int citySize;
	public int departureCityNo;
	public int goalCityNo;
	public double meanPathCost;
	public double maxPathCost;
	public double minPathCost;
	
	public Problem(){
	}
	
	public void init(List<City> cityList, int departureCityNo, int goalCityNo) throws Exception{
		this.cityList = cityList;
		this.citySize = (int) cityList.size();
		this.departureCityNo = departureCityNo;
		this.goalCityNo = goalCityNo;
		
		initCityPath();
	}
	
	/**
	 * TODO 아래 로직 문제가 없는지?
	 * 가장 가까운 도시를 찾지 못하면 목적지를 리턴한다.
	 * @param fromCityNo
	 * @param exceptCityNo
	 * @return
	 * @throws Exception 
	 */
	public int getClosestCityNo(int fromCityNo, List<Integer> exceptCityNo) throws Exception{
		int closestCityNo = goalCityNo;
		double shortest = Double.MAX_VALUE;
		int tempCityNo;
		for(int i=0; i< this.citySize; i++){	//  i가 cityNo를 의미하지 않는다.
			tempCityNo = cityList.get(i).getCityNo();
			if(exceptCityNo.contains(tempCityNo)){
				continue;
			}
			
			double fromToPathCost = calculateDistance(getCity(fromCityNo),getCity(tempCityNo));
			if(fromToPathCost < shortest){
				shortest = fromToPathCost;
				closestCityNo = tempCityNo;
			}
		}
		
		return closestCityNo;
	}
	
	
	private void initCityPath() throws Exception{
		double meanDistance = 0.0;
		double min = Double.MAX_VALUE;
		double max = 0.0;
		double dis = 0.0;
		int count = 0;
		
		// Map 이므로 i, j가 cityNo와 동일한 의미가 아님.
		for(int i=0; i<this.citySize; i++){
			for(int j=0; j<this.citySize; j++){
				
				if(i == j){ // 동일 도시의 거리는 무조건 0
				} else {
					// mean, min, max values
//					dis = distances[i][j].getDistance();
					dis = calculateDistance(this.cityList.get(i), this.cityList.get(j));
					meanDistance += dis;
					if(dis < min){
						min = dis;
					}
					if(max < dis){
						max = dis;
					}
					count++;
				}
			}
		}
		
		this.maxPathCost = TspUtil.formatDouble(max);
		this.minPathCost = TspUtil.formatDouble(min);
		this.meanPathCost = TspUtil.formatDouble(meanDistance/count);
	}
	
	public boolean checkGoal(CityPath cityPath) {
		if(cityPath.getPathCost() == 0.0){
			return false;
		}
		
		if(cityPath.tourHistory.size() != this.citySize + 1){
			return false;
		}
		
		return true;
	}
	
	public  City getCity(int cityNo){
		for(int i=0; i<this.citySize; i++){
			if(this.cityList.get(i).getCityNo() == cityNo){
				return this.cityList.get(i);
			}
		}
		
		return null;
	}
	
//	public double getDistance(int fromCityNo, int toCityNo){
//		return calculateDistance(getCity(fromCityNo), getCity(toCityNo));
//	}
	
	public double calculateDistance(City one, City two) throws Exception{
		if(one == null || two == null){
			throw new Exception("Problem.caluclateDistance(City, City) : parameter is null");
		}
		double a = Math.abs(two.getX() - one.getX());
		double b = Math.abs(two.getY() - one.getY());
		return  Math.sqrt(a*a + b*b);
	}
	
	public City getDeparture(){
		return this.getCity(this.departureCityNo);
	}	
}
